﻿using System;
using System.Linq;
using System.ServiceProcess;
using System.Threading;
using System.Net;
using System.Net.Sockets;

namespace GPSITrackProtocolListenerWindowsService
{    
     // State object for reading client data asynchronously
    public class StateObject
    {
        // Client socket.
        public Socket workSocket = null;
        // Size of receive buffer.
        public const int BufferSize = 512;
        // Receive buffer.
        public byte[] buffer = new byte[BufferSize];            
    }

    public partial class GPSiTracProtocolListenerServerService : ServiceBase
    {

        //Message Queue
        [Serializable]
        public class GPSMessageQueue
        {
            public string Imei;
            public GPSTrackingDBV2.Entities.TrackDataLastLocation TrackDataLastLocationEntity;
        }

        string _MessageQueueName = @Properties.Settings.Default.MessageQueueName;

        public static Thread TCPListenerThread = new Thread(StartListening);

        private static bool IsThreadAlive = true;

        // Thread signal.
        public static ManualResetEvent allDone = new ManualResetEvent(false);

        public GPSiTracProtocolListenerServerService()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            try
            {
                GPSTrackingDBCustom.Log.WindowsServiceLog.Info("--- GPS Tracking Server Start Listening AL V2[900S] V1 ---", "AL V2[900S]");
                GPSTrackingDBCustom.Log.WindowsServiceLog.Info("IP: " + System.Net.Dns.GetHostName(), "AL V2[900S]");
                GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Port: " + Properties.Settings.Default.Port.ToString(), "AL V2[900S]");
                GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Max Connections: " + Properties.Settings.Default.MaxConnections.ToString(), "AL V2[900S]");

                IsThreadAlive = true;
                TCPListenerThread.Priority = ThreadPriority.Highest;
                TCPListenerThread.IsBackground = true;
                TCPListenerThread.Start();
            }
            catch (Exception ex)
            {
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error - Message:" + ex.Message, "AL V2[900S]");
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error("AL V2[900S] Server is DOWN", "AL V2[900S]");
            }

        }

        protected override void OnStop()
        {
            IsThreadAlive = false;
            GPSTrackingDBCustom.Log.WindowsServiceLog.Warning("--- GPS Tracking Server AL V2[900S] Is DOWN ---", "AL V2[900S]");
        }

        public static void StartListening()
        {
            IPEndPoint ep = new IPEndPoint(IPAddress.Any, Properties.Settings.Default.Port);

            // Create a TCP/IP socket for listner.
            Socket listenerSock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // Bind the socket to the endpoint and wait for listen for incoming connections.
            try
            {
                listenerSock.Bind(ep);
                listenerSock.Listen(Properties.Settings.Default.MaxConnections);

                while (IsThreadAlive)
                {
                    try
                    {
                        // Set the event to nonsignaled state.
                        allDone.Reset();

                        // Start an asynchronous socket to listen for connections.
                        GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Waiting for a connection AL V2[900S]  ... ", "AL V2[900S]");
                        listenerSock.BeginAccept(new AsyncCallback(AcceptCallback), listenerSock);

                        // Wait until a connection is made before continuing.
                        allDone.WaitOne();
                    }
                    catch (Exception ex)
                    {
                        GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error -  Waiting for a connection..... Message:" + ex.Message, "AL V2[900S]");
                        listenerSock.Shutdown(SocketShutdown.Both);
                        listenerSock.Close(); 
                    }

                }

            }
            catch (Exception ex)
            {
                IsThreadAlive = false;
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error - StartListening Message:" + ex.Message, "AL V2[900S]");                
                GPSTrackingDBCustom.Log.WindowsServiceLog.Warning("--- GPS Tracking Server AL V2[900S] Is DOWN ---", "AL V2[900S]");
            }
        }

        public static void AcceptCallback(IAsyncResult ar)
        {
            try
            {
                // Signal the main thread to continue.
                allDone.Set();

                // Get the socket that handles the client request.
                Socket listener = (Socket)ar.AsyncState;
                Socket handler = listener.EndAccept(ar);

                // Create the state object.
                StateObject MessageDevice = new StateObject();
                MessageDevice.workSocket = handler;
                handler.BeginReceive(MessageDevice.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), MessageDevice);
            }            
            catch (Exception ex)
            {
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error - AcceptCallback Message:" + ex.Message, "AL V2[900S]");                
            }
        }

        public static void ReadCallback(IAsyncResult ar)
        {
            // Retrieve the state object and the handler socket
            // from the asynchronous state object.
            StateObject MessageDevice = (StateObject)ar.AsyncState;
            Socket handler = MessageDevice.workSocket;

            // Read data from the client socket.
            int bytesRead = handler.EndReceive(ar);

            GPSiTracProtocol.GPSiTracProtocol900C.MessageDevice = MessageDevice.buffer;
            GPSiTracProtocol.GPSiTracProtocol900C.GPSDeviceID =  GPSiTracProtocol.GPSiTracProtocol900C.GetGPSID();

            string MessageDeviceHex = GPSiTracProtocol.GPSiTracProtocol900C.ConvertToHexString(MessageDevice.buffer, bytesRead);           


            if (bytesRead > 0)
            {
                if (MessageDevice.buffer[2] == (byte)GPSiTracProtocol.GPSiTracProtocol900C.GPSDeviceCommandWord.Received ||
                    MessageDevice.buffer[2] == (byte)GPSiTracProtocol.GPSiTracProtocol900C.GPSDeviceCommandWord.CallRollData)
                {
                    handler.Shutdown(SocketShutdown.Both);
                    handler.Close();

                    return;
                }

                if (MessageDevice.buffer[2] == (byte)GPSiTracProtocol.GPSiTracProtocol900C.GPSDeviceCommandWord.LinkCenter || 
                    MessageDevice.buffer[2] == (byte)GPSiTracProtocol.GPSiTracProtocol900C.GPSDeviceCommandWord.Handshake)               
                {                    
                    GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Recieved From Imei:" + GPSiTracProtocol.GPSiTracProtocol900C.GetImei() + " IP:" + handler.RemoteEndPoint, "AL V2[900S]");
                    GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Message Device: " + MessageDeviceHex, "AL V2[900S]");
                    
                    // Not all data received. Get more.
                    handler.BeginReceive(MessageDevice.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), MessageDevice);

                }
                else
                {
                    GPSTrackingDBV2.Entities.TrackData TrackDataEntity = new GPSTrackingDBV2.Entities.TrackData();

                    GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Message Device: " + MessageDeviceHex, "AL V2[900S]");
                    //Insert to Database
                    try
                    {
                        GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Inserting track message to database ...", "AL V2[900S]");                        
                        GPSTrackingDBV2.Entities.TrackDataLastLocation TrackDataLastLocationEntity = new GPSTrackingDBV2.Entities.TrackDataLastLocation();

                        TrackDataLastLocationEntity.GpsImei = TrackDataEntity.GpsImei = GPSiTracProtocol.GPSiTracProtocol900C.GetImei().ToString();
                        TrackDataLastLocationEntity.DateTime = TrackDataEntity.DateTime = DateTime.Now;
                        TrackDataLastLocationEntity.Latitude = TrackDataEntity.Latitude = GPSiTracProtocol.GPSiTracProtocol900C.GetCoordinate(GPSiTracProtocol.GPSiTracProtocol900C.MessageCoordinate.Latitude);
                        TrackDataLastLocationEntity.Longitude = TrackDataEntity.Longitude = GPSiTracProtocol.GPSiTracProtocol900C.GetCoordinate( GPSiTracProtocol.GPSiTracProtocol900C.MessageCoordinate.Longitude);
                        TrackDataLastLocationEntity.Heading = TrackDataEntity.Heading = GPSiTracProtocol.GPSiTracProtocol900C.GetHeading();
                        TrackDataLastLocationEntity.Speed = TrackDataEntity.Speed = GPSiTracProtocol.GPSiTracProtocol900C.GetSpeed();
                        

                        GPSTrackingDBV2.Data.DataRepository.TrackDataProvider.Insert(TrackDataEntity);


                        //Update last location  
                        GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Updating last track message location to database ...", "AL V2[900S]");
                        try
                        {
                          GPSTrackingDBV2Custom.Data.DataRepository.TrackDataLastLocationProvider.UpdateOrInsert(TrackDataLastLocationEntity);                        
                        }
                        catch (Exception ex)
                        {
                            GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error - Updating last track message:" + ex.Message, "AL V2[900S]");
                            // Disconnect 
                            handler.Shutdown(SocketShutdown.Both);
                            handler.Close();     
                        }

                        //Notifier Queue
                        if (Properties.Settings.Default.EnabledMessageQueueName)
                        {
                            SendMQ(TrackDataLastLocationEntity);
                        }


                    }
                    catch (Exception ex)
                    {
                        GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error - Inserting track message:" + ex.Message, "AL V2[900S]");
                        // Disconnect 
                        handler.Shutdown(SocketShutdown.Both);
                        handler.Close();     
                    }
                    
                    // Message OK
                    byte[] MessageSend = new byte[0];
                    MessageSend = GPSiTracProtocol.GPSiTracProtocol900C.SendOK();
                   
                    // Message Send
                    if (MessageSend.Length > 0)
                        Send(handler, MessageSend );
                }
                                
            }

        }

        private static void Send(Socket handler, byte[] MessageSend)
        {
            // Begin sending the data to the remote device.
            GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Message Send: " + GPSiTracProtocol.GPSiTracProtocol900C.ConvertToHexString(MessageSend, -1), "AL V2[900S]");
            handler.BeginSend(MessageSend, 0, MessageSend.Length, 0, new AsyncCallback(SendCallback), handler);
        }

        private static void SendCallback(IAsyncResult ar)
        {
            // Retrieve the socket from the state object.
            Socket handler = (Socket)ar.AsyncState;

            try
            {
                
                // Complete sending the data to the remote device.
                int bytesSent = handler.EndSend(ar);
                GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Disconnected... Send " + bytesSent.ToString() + " bytes to gps device.", "AL V2[900S]");
                handler.Shutdown(SocketShutdown.Both);
                handler.Close();

            }
            catch (Exception ex)
            {
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error - SendCallBack..... " + ex.Message, "AL V2[900S]");
                // Disconnect 
                handler.Shutdown(SocketShutdown.Both);
                handler.Close();     
            }
        }

        private static void SendCommand(Socket handler, byte[] MessageSend)
        {
            // Begin sending the data to the remote device.
            GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Command Send: " + GPSiTracProtocol.GPSiTracProtocol900C.ConvertToHexString(MessageSend, -1), "AL V2[900S]");
            handler.BeginSend(MessageSend, 0, MessageSend.Length, 0, new AsyncCallback(SendCallbackCommand), handler);
        }

        private static void SendCallbackCommand(IAsyncResult ar)
        {
            // Retrieve the socket from the state object.
            Socket handler = (Socket)ar.AsyncState;

            try
            {       
                // Complete sending the data to the remote device.
                int bytesSent = handler.EndSend(ar);
                GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Command... Send " + bytesSent.ToString() + " bytes to gps device.", "AL V2[900S]");

            }
            catch (Exception ex)
            {
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Error - Command..... " + ex.StackTrace, "AL V2[900S]");
                // Disconnect 
                handler.Shutdown(SocketShutdown.Both);
                handler.Close();     
            }
        }

        public static void SendMQ(GPSTrackingDBV2.Entities.TrackDataLastLocation trackDataLastLocationEntity)
        {

            string MessageQueueName = @Properties.Settings.Default.MessageQueueName;
            if (System.Messaging.MessageQueue.Exists(MessageQueueName))
            {
                System.Messaging.MessageQueue MessageQueue = new System.Messaging.MessageQueue(MessageQueueName);
                try
                {
                    GPSMessageQueue GPSMessageQueue = new GPSMessageQueue()
                    {
                        Imei = trackDataLastLocationEntity.GpsImei,
                        TrackDataLastLocationEntity = trackDataLastLocationEntity
                    };

                    MessageQueue.Send(GPSMessageQueue);
                    GPSTrackingDBCustom.Log.WindowsServiceLog.Info("Message Queue: OK", "AL V2[900S]");

                }
                catch (System.Messaging.MessageQueueException ex)
                {
                    GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Message Queue Exception:" + ex.Message, "Windows Mobile");
                }
                finally
                {
                    MessageQueue.Close();
                }
            }
            else
            {
                GPSTrackingDBCustom.Log.WindowsServiceLog.Error("Message Queue Exception: Queue not found!", "Windows Mobile");
            }
        }
    }
}
